from typing import Tuple, Any

import numpy as np
import pandas as pd
import sympy
from numpy import ndarray
from pandas import DataFrame
from sklearn.metrics import mean_absolute_error
from sklearn.metrics import mean_squared_error
from sklearn.metrics import mean_absolute_percentage_error
from sklearn.metrics import r2_score

from app.models.TimeSequenceModel import TimeSequenceModel


class StaticModel:

    @staticmethod
    def get_fail_sum(fail_dataframe) -> int:
        return len(fail_dataframe["value"])

    @staticmethod
    def get_unreliability(fail_dataframe, para, func_F, diff=False) -> DataFrame:
        t, dt = sympy.symbols("t dt", position=True)
        update_func_F = func_F.subs(para)
        fail_list = fail_dataframe["value"].tolist()
        unreliability_list = []
        if diff is True:
            diff_fail_list = TimeSequenceModel.differ_fail(fail_dataframe)["value"].tolist()
            for item in fail_list:
                idx = fail_list.index(item)
                unreliability_list.append(update_func_F.subs({t: item, dt: diff_fail_list[idx]}))
        else:
            unreliability_list = [update_func_F.subs(t, item) for item in fail_list]
        return pd.DataFrame({"unreliability", unreliability_list})

    @staticmethod
    def get_reliability(fail_dataframe, para, func_R, diff=False) -> DataFrame:
        t, dt = sympy.symbols("t dt", position=True)
        fail_list = fail_dataframe["value"].tolist()
        update_func_R = func_R.subs(para)
        reliability_list = []
        if diff is True:
            diff_fail_list = TimeSequenceModel.differ_fail(fail_dataframe)["value"].tolist()
            for item in fail_list:
                idx = fail_list.index(item)
                reliability_list.append(update_func_R.subs({t: item, dt: diff_fail_list[idx]}))
        else:
            reliability_list = [update_func_R.subs(t, item) for item in fail_list]
        return pd.DataFrame({"reliability", reliability_list})

    @staticmethod
    def get_failure_probability(fail_dataframe, para, func_f, diff=False) -> DataFrame:
        t, dt = sympy.symbols("t dt", position=True)
        fail_list = fail_dataframe["value"].tolist()
        update_func_f = func_f.subs(para)
        failure_probability_list = []
        if diff is True:
            diff_fail_list = TimeSequenceModel.differ_fail(fail_dataframe)["value"].tolist()
            for item in fail_list:
                idx = fail_list.index(item)
                failure_probability_list.append(update_func_f.subs({t: item, dt: diff_fail_list[idx]}))
        else:
            failure_probability_list = [update_func_f.subs(t, item) for item in fail_list]
        return pd.DataFrame({"failure_probability", failure_probability_list})

    @staticmethod
    def get_failure_signature(fail_dataframe, para, func_set, diff=False) -> Tuple[Any, Any]:
        reliability_dataframe = StaticModel.get_reliability(fail_dataframe, para, func_set["func_F"], diff)
        unreliability_dataframe = StaticModel.get_unreliability(fail_dataframe, para, func_set["func_R"], diff)
        failure_probability_dataframe = StaticModel.get_reliability(fail_dataframe, para, func_set["func_f"], diff)

        RF_dataframe = pd.concat([reliability_dataframe, unreliability_dataframe], axis=0, join="inner")
        return pd.concat([RF_dataframe, failure_probability_dataframe], axis=0, join="in")

    @staticmethod
    def get_MTTF(fail_dataframe) -> ndarray:
        return np.mean(TimeSequenceModel.differ_fail(fail_dataframe)["value"])

    @staticmethod
    def __get_true_predict_data(fail_test_dataframe) -> Tuple[Any, Any]:
        return fail_test_dataframe["true"], fail_test_dataframe["predict"]

    @staticmethod
    def get_MAE(fail_test_dataframe) -> float:
        fail_true, fail_predict = StaticModel.__get_true_predict_data(fail_test_dataframe)
        return mean_absolute_error(fail_true, fail_predict)

    @staticmethod
    def get_MSE(fail_test_dataframe) -> float:
        fail_true, fail_predict = StaticModel.__get_true_predict_data(fail_test_dataframe)
        return mean_squared_error(fail_true, fail_predict)

    @staticmethod
    def get_MAPE(fail_test_dataframe) -> float:
        fail_true, fail_predict = StaticModel.__get_true_predict_data(fail_test_dataframe)
        return mean_absolute_percentage_error(fail_true, fail_predict)

    @staticmethod
    def get_r2_score(fail_test_dataframe) -> float:
        fail_true, fail_predict = StaticModel.__get_true_predict_data(fail_test_dataframe)
        return r2_score(fail_true, fail_predict)


if __name__ == "__main__":
    df = pd.DataFrame({"value": [1, 2, 5, 7, 15]})
    print(StaticModel.get_MTTF(df))
